-
Notifications
You must be signed in to change notification settings - Fork 10
Register Hub CE #141
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Register Hub CE #141
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Adds a new /hub/register page for registering a Cryptomator Hub instance for a Community Edition license, including a multi-step UI flow, supporting JS logic, and new i18n strings.
Changes:
- Introduces a new Hub CE registration page layout with a step-based AlpineJS UI and Altcha captcha integration.
- Adds new frontend logic (
hubce.js) to drive the registration/verification/license-fetch flow. - Adds page content entries for EN/DE and new EN translations for the CE registration flow.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 11 comments.
Show a summary per file
| File | Description |
|---|---|
| layouts/hub-register/single.html | New multi-step registration UI, captcha widgets, and script includes for the Hub CE flow |
| assets/js/hubce.js | New client-side flow logic: email confirmation request + license refresh call |
| i18n/en.yaml | Adds English strings for the new Hub CE registration UI |
| content/hub-register.en.html | Registers the EN page route/type for /hub/register |
| content/hub-register.de.html | Registers the DE page route/type for /de/hub/register |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| <form x-ref="form" class="md:col-span-2" @submit.prevent="hubCE.submit(); $refs.captcha.reset()"> | ||
| <!-- Step 1: Email Address --> |
Copilot
AI
Jan 29, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pressing Enter in the email field (step 1) will submit the form, but $refs.captcha isn’t rendered in that step; calling $refs.captcha.reset() will throw and break the flow. Also, Enter bypasses the disabled “Next” button guard for oldLicense, allowing progression without the required token. Consider guarding the reset (e.g., only when the ref exists) and enforcing oldLicense in validation / making the step-1 navigation button type="button" so the form can’t submit in step 1.
| <button :disabled="feedbackData.inProgress || !acceptTerms || captchaState == 'verifying'" type="submit" class="btn btn-primary w-full md:w-64" data-umami-event="hub-managed-form" x-cloak> | ||
| <i :class="{'fa-paper-plane': !feedbackData.inProgress, 'fa-spinner fa-spin': feedbackData.inProgress}" class="fa-solid" aria-hidden="true"></i> | ||
| {{ i18n "hub_managed_step_4_submit" }} |
Copilot
AI
Jan 29, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This submit button still uses Hub Managed copy/analytics (hub_managed_step_4_submit, hub-managed-form) on the CE registration flow. That will show the wrong label (and mix Umami events). Add CE-specific i18n/event names or reuse the CE ..._steps_next/submit wording consistently.
| <button :disabled="feedbackData.inProgress || !acceptTerms || captchaState == 'verifying'" type="submit" class="btn btn-primary w-full md:w-64" data-umami-event="hub-managed-form" x-cloak> | |
| <i :class="{'fa-paper-plane': !feedbackData.inProgress, 'fa-spinner fa-spin': feedbackData.inProgress}" class="fa-solid" aria-hidden="true"></i> | |
| {{ i18n "hub_managed_step_4_submit" }} | |
| <button :disabled="feedbackData.inProgress || !acceptTerms || captchaState == 'verifying'" type="submit" class="btn btn-primary w-full md:w-64" data-umami-event="hub-ce-registration-step-2-submit" x-cloak> | |
| <i :class="{'fa-paper-plane': !feedbackData.inProgress, 'fa-spinner fa-spin': feedbackData.inProgress}" class="fa-solid" aria-hidden="true"></i> | |
| {{ i18n "hub_ce_registration_steps_submit" }} |
| <p>Todo: Please check your e-mails.</p> | ||
| </div> | ||
| </div> | ||
| </template> | ||
|
|
||
| <!-- Step 3b: License Key (success) --> | ||
| <template x-if="feedbackData.currentStep == 2 && feedbackData.success"> | ||
| <div class="md:col-span-2 grid grid-cols-6 gap-6"> | ||
| <div class="flex flex-col col-span-6 lg:col-span-4"> | ||
| <p class="hidden md:block font-p text-sm text-gray-500 mb-2"> | ||
| {{ i18n "hub_managed_steps_title" | safeHTML }} | ||
| </p> | ||
| <h2 class="font-h2 mb-6"> | ||
| {{ i18n "hub_ce_registration_step_3_license_title" }} | ||
| </h2> | ||
| <p>Todo: return URL</p> | ||
| {{ $challengeUrl := printf "%s/licenses/hub/challenge" .Site.Params.apiBaseUrl }} | ||
| {{ partial "captcha.html" (dict "challengeUrl" $challengeUrl "captchaPayload" "submitData.captcha" "captchaState" "captchaState") }} | ||
| <textarea class="block input-box w-full h-48 mb-8" x-text="feedbackData.licenseText" readonly></textarea> | ||
|
|
||
| <div class="mt-auto"> | ||
| <p :class="{'hidden': !feedbackData.errorMessage}" class="text-sm text-red-600 mb-2" x-text="feedbackData.errorMessage"></p> | ||
| <button class="btn btn-primary w-full md:w-64" data-umami-event="hub-ce-registration-step-1"> | ||
| <i :class="{'fa-chevron-right': !feedbackData.inProgress, 'fa-spinner fa-spin': feedbackData.inProgress}" class="fa-solid" aria-hidden="true"></i> | ||
| Todo: Return to Hub | ||
| </button> |
Copilot
AI
Jan 29, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are user-facing placeholder strings (Todo: ...) and a “Return to Hub” button that currently lives inside the form and will submit the form (triggering hubCE.submit()), not actually return anywhere. These placeholders/actions should be implemented (or removed) before merging to avoid shipping incomplete UX and unintended behavior.
| <p>Todo: return URL</p> | ||
| {{ $challengeUrl := printf "%s/licenses/hub/challenge" .Site.Params.apiBaseUrl }} | ||
| {{ partial "captcha.html" (dict "challengeUrl" $challengeUrl "captchaPayload" "submitData.captcha" "captchaState" "captchaState") }} | ||
| <textarea class="block input-box w-full h-48 mb-8" x-text="feedbackData.licenseText" readonly></textarea> |
Copilot
AI
Jan 29, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
<textarea ... x-text="feedbackData.licenseText"> won’t update the textarea’s value reliably (the project generally uses x-model/:value for textareas). Bind the textarea’s value (e.g., x-model to a readonly field or :value) so the license text updates when the AJAX call completes.
| <textarea class="block input-box w-full h-48 mb-8" x-text="feedbackData.licenseText" readonly></textarea> | |
| <textarea class="block input-box w-full h-48 mb-8" :value="feedbackData.licenseText" readonly></textarea> |
| {{ partial "altcha-css.html" . }} | ||
| {{ end }} | ||
| {{ define "main" }} | ||
| <section x-data="{steps: ['{{ i18n "hub_ce_registration_step_1_nav_title" }}', '{{ i18n "hub_ce_registration_step_2_confirmation_nav_title" }}', '{{ i18n "hub_ce_registration_step_3_license_nav_title" }}'], feedbackData: {currentStep: 0, success: false, inProgress: false, errorMessage: '', licenseText: null}, submitData: {captcha: null, oldLicense: '', email: '', acceptNewsletter: false}, acceptTerms: false, hubCE: null, captchaState: null}" x-init="hubCE = new HubCE($refs.form, feedbackData, submitData, new URLSearchParams(location.hash.substring(1)))" class="container py-12"> |
Copilot
AI
Jan 29, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The CE registration flow is described as 4 steps (email → accept terms → verify email → get license), but the UI steps array only has 3 steps and there’s no dedicated “verify email” step in the progress indicator. Either align the UI to the described 4-step process or update the workflow/labels to match the implemented behavior.
| getHubLicense() { | ||
| $.ajax({ | ||
| url: REFRESH_LICENSE_URL, | ||
| type: 'POST', | ||
| data: { | ||
| token: this._submitData.oldLicense | ||
| } | ||
| }).done(response => { | ||
| this._feedbackData.licenseText = response; | ||
| }).fail(xhr => { | ||
| this.onRequestFailed(xhr.responseJSON?.message || 'Fetching license failed.'); | ||
| }); | ||
| } |
Copilot
AI
Jan 29, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
getHubLicense() doesn’t set inProgress/clear errorMessage, so the UI can’t reliably show loading state or prevent duplicate submits. Also, the template renders an Altcha captcha for the license step, but this request doesn’t send any captcha payload—either remove the captcha from the license step or include its payload in the POST so the widget has an effect.
| <div class="grid grid-cols-6 gap-6"> | ||
| <div class="flex flex-col col-span-6 lg:col-span-4"> | ||
| <p class="hidden md:block font-p text-sm text-gray-500 mb-2"> | ||
| {{ i18n "hub_managed_steps_title" | safeHTML }} |
Copilot
AI
Jan 29, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The steps title inside the step content uses hub_managed_steps_title, but this page introduces hub_ce_registration_steps_title. This makes the new CE i18n key unused and risks inconsistent translations between mobile/desktop. Switch these occurrences to the CE-specific key (or remove the CE key if you intend to reuse the managed one).
| {{ i18n "hub_managed_steps_title" | safeHTML }} | |
| {{ i18n "hub_ce_registration_steps_title" | safeHTML }} |
| <script type="text/javascript" src="{{ $hubCeJs.RelPermalink }}" integrity="{{ $hubCeJs.Data.Integrity }}" defer></script> | ||
| {{ $altchaJs := resources.Get "js/altcha/altcha.js" | minify | fingerprint }} | ||
| <script type="module" src="{{ $altchaJs.RelPermalink }}" integrity="{{ $altchaJs.Data.Integrity }}" defer></script> | ||
| {{ $altchaWorkerJs := resources.Get "js/altcha/worker.js" }} |
Copilot
AI
Jan 29, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In production, altchaWorkerJs isn’t minify|fingerprint’d but an integrity attribute is still emitted. This will likely produce an empty/incorrect integrity value and prevents cache-busting. Apply the same minify | fingerprint pipeline as the other scripts (or drop integrity if you intentionally don’t fingerprint).
| {{ $altchaWorkerJs := resources.Get "js/altcha/worker.js" }} | |
| {{ $altchaWorkerJs := resources.Get "js/altcha/worker.js" | minify | fingerprint }} |
| $(this._form).find(':input').addClass('show-invalid'); | ||
| this._feedbackData.errorMessage = 'Please fill in all required fields.'; | ||
| return; | ||
| } |
Copilot
AI
Jan 29, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
validateEmail() allows progression even when oldLicense is missing (it’s only blocked by the disabled button in the HTML). Because the form can be submitted via Enter key, this path can advance with a null/empty token and later POST it to the backend. Add an explicit oldLicense check here (and surface a clear error) to match the UI constraint.
| } | |
| } | |
| // Ensure oldLicense is present before allowing progression. | |
| const oldLicense = this._submitData.oldLicense; | |
| if (!oldLicense) { | |
| this._feedbackData.inProgress = false; | |
| this._feedbackData.errorMessage = 'License information is missing. Please open this page using the link provided in your email.'; | |
| return; | |
| } |
| const VERIFY_EMAIL_URL = API_BASE_URL + '/connect/email/verify'; | ||
| const REFRESH_LICENSE_URL = API_BASE_URL + '/licenses/hub/refresh'; | ||
|
|
||
| class HubCE { |
Copilot
AI
Jan 29, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unused class HubCE.
This is the website counterpart of https://github.com/cryptomator/api/pull/12.
It adds the new page
/hub/register, which has a four step process:POST /licenses/hub/refreshendpoint to trade in trial license for new CE licenseNote
In order to start this workflow, the old trial license must be passed to the site via the url fragment, e.g.
/hub/register#oldLicense=ey...This is just the bare minimum, I imagine to improve this workflow by integrating it with Hub via automatic redirect.